﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Tao.Cg;
using OpenTK.Graphics;
using OpenTK.Math;
using System.Drawing;

namespace Rekx.Graphics
{
    /// <summary>
    /// This is a basic effect used to apply shading
    /// </summary>
    public class Effect : IDisposable
    {
        #region Member Variables

        IntPtr effect = IntPtr.Zero;
        IntPtr currentTechnique = IntPtr.Zero;
                
      
        List<Pass> Passes = new List<Pass>();

        #endregion 

        #region Construction
        internal Effect(IntPtr effect)
        {
            this.effect = effect;

            
            // validate technique and choose the appropriate, i.e. best.
            // techniques should be sorted from best to worst in the effect file

            currentTechnique = Cg.cgGetFirstTechnique(effect);
            //while (Cg.cgValidateTechnique(currentTechnique) == Cg.CG_FALSE)
            //{
            //     // technique not valid on this configuration
            //     // get next
            //     currentTechnique = Cg.cgGetNextTechnique(currentTechnique);                
            //}

            // get all passes from the effect file and save them
            IntPtr pass = Cg.cgGetFirstPass(currentTechnique);
            while (pass != IntPtr.Zero)
            {
                Passes.Add(new Pass(pass));
                pass = Cg.cgGetNextPass(pass);
            }
        }
        
        #endregion

          
        
             

        #region ParameterHandling

        /// <summary>
        /// Sets the value of a given semantic or named variable
        /// </summary>
        /// <param name="name">Name of semantic or variable</param>
        /// <param name="value">Vector3 to set it to</param>
        public void SetValue(string name, ref Vector3 value)
        {
            // TODO:  check for type as well..
            Cg.cgSetParameter3fv(GetParameter(name), out value.X);            
        }


        /// <summary>
        /// Sets the value of a given semantic or named variable
        /// </summary>
        /// <param name="name">Name of semantic or variable</param>
        /// <param name="value">Float to set it to</param>
        public void SetValue(string name, ref float value)
        {
            Cg.cgSetParameter1f(GetParameter(name), value);
        }



        /// <summary>
        /// Sets the value of a given semantic or named variable
        /// </summary>
        /// <param name="name">Name of the semantic or variable</param>
        /// <param name="matrix">Matrix to set it to</param>
        public void SetValue(string name, ref Matrix4 matrix)
        {
            Cg.cgSetMatrixParameterfc(GetParameter(name), out matrix.Row0.X);
        }


        private IntPtr GetParameter(string name)
        {
            IntPtr param  = Cg.cgGetEffectParameterBySemantic(effect, name);

            
            // see if it is not valid, and then try to obtain it from variable name if it is the case
            if (Cg.cgIsParameter(param) != true)
            {
                param = Cg.cgGetNamedEffectParameter(effect, name);
                // return if it is not a parameter this time either
                if (Cg.cgIsParameter(param) != true)
                {
                    Cg.cgDestroyParameter(param);
                    return IntPtr.Zero;
                }
            }

            return param;
        }





        #endregion


     



        #region IDisposable Pattern
        // taken from msdn

        private bool disposed = false;

        // Implement IDisposable.
        // Do not make this method virtual.
        // A derived class should not be able to override this method.
        /// <summary>
        /// disposes it
        /// </summary>
        public void Dispose()
        {
            Dispose(true);
            // This object will be cleaned up by the Dispose method.
            // Therefore, you should call GC.SupressFinalize to
            // take this object off the finalization queue
            // and prevent finalization code for this object
            // from executing a second time.
            GC.SuppressFinalize(this);
        }

        // Dispose(bool disposing) executes in two distinct scenarios.
        // If disposing equals true, the method has been called directly
        // or indirectly by a user's code. Managed and unmanaged resources
        // can be disposed.
        // If disposing equals false, the method has been called by the
        // runtime from inside the finalizer and you should not reference
        // other objects. Only unmanaged resources can be disposed.
        private void Dispose(bool disposing)
        {
            // Check to see if Dispose has already been called.
            if (!this.disposed)
            {
                // If disposing equals true, dispose all managed
                // and unmanaged resources.
                if (disposing)
                {
                    // Dispose managed resources.

                }

                // Call the appropriate methods to clean up
                // unmanaged resources here.
                // If disposing is false,
                // only the following code is executed.
                Cg.cgDestroyEffect(effect);

                // Note disposing has been done.
                disposed = true;

            }
        }

        #endregion

        #region Enumerable
        /// <summary>
        /// Returns an enumerator that iterates through the passes of the technique applied
        /// </summary>
        /// <returns>The enumerator used for foreach</returns>
        public List<Pass>.Enumerator GetEnumerator()
        {
            return Passes.GetEnumerator();
        }
        #endregion
    }



    /// <summary>
    /// Represents a render pass
    /// </summary>
    public class Pass
    {
        IntPtr pass = IntPtr.Zero;

        internal Pass(IntPtr pass)
        {
            this.pass = pass;
        }

        /// <summary>
        /// Begins the current render pass
        /// </summary>
        public void Begin()
        {
            Cg.cgSetPassState(pass);
        }

        /// <summary>
        /// Ends the current render pass
        /// </summary>
        public void End()
        {
            Cg.cgResetPassState(pass);
        }
    }

    /// <summary>
    /// Creates a shader context which saves and manages Effects
    /// </summary>
    public class ShaderContext : IDisposable
    {
        private IntPtr context = IntPtr.Zero;

        /// <summary>
        /// Creates the shader context
        /// </summary>
        public ShaderContext()
        {
            context = Cg.cgCreateContext();
            CgGl.cgGLRegisterStates(context);
            CgGl.cgGLSetManageTextureParameters(context, true);
        }

        /// <summary>
        /// Creates a new effect from file with the associated texture
        /// </summary>
        /// <param name="filename">Path to the effect file</param>
         /// <returns></returns>
        public Effect CreateEffectFromFile(string filename)
        {
            return new Effect(Cg.cgCreateEffectFromFile(context, filename, null));
        }


        #region IDisposable Pattern
        // taken from msdn

        private bool disposed = false;

        // Implement IDisposable.
        // Do not make this method virtual.
        // A derived class should not be able to override this method.
        /// <summary>
        /// Disposes it
        /// </summary>
        public void Dispose()
        {
            Dispose(true);
            // This object will be cleaned up by the Dispose method.
            // Therefore, you should call GC.SupressFinalize to
            // take this object off the finalization queue
            // and prevent finalization code for this object
            // from executing a second time.
            GC.SuppressFinalize(this);
        }

        // Dispose(bool disposing) executes in two distinct scenarios.
        // If disposing equals true, the method has been called directly
        // or indirectly by a user's code. Managed and unmanaged resources
        // can be disposed.
        // If disposing equals false, the method has been called by the
        // runtime from inside the finalizer and you should not reference
        // other objects. Only unmanaged resources can be disposed.
        private void Dispose(bool disposing)
        {
            // Check to see if Dispose has already been called.
            if (!this.disposed)
            {
                // If disposing equals true, dispose all managed
                // and unmanaged resources.
                if (disposing)
                {
                    // Dispose managed resources.

                }

                
                // Call the appropriate methods to clean up
                // unmanaged resources here.
                // If disposing is false,
                // only the following code is executed.
                Cg.cgDestroyContext(context);

                // Note disposing has been done.
                disposed = true;

            }
        }

        #endregion
    }


}
